/*
 * Copyright 2024 The CHANGLEI Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

part of '../px.dart';

/// 像素大小
extension SizeToPx on Size {
  /// 像素大小
  PxSize get px => PxSize(width.px, height.px);

  /// 像素大小
  PxSize toPx(BuildContext context) {
    return PxSize(
      width.toPx(context),
      height.toPx(context),
    );
  }
}

class _PxSizeFromRadius extends PxSize {
  const _PxSizeFromRadius(this.radius) : super(PxDouble.zero, PxDouble.zero);

  final PxDouble radius;

  @override
  PxDouble get _dx => radius * 2.px;

  @override
  PxDouble get _dy => radius * 2.px;
}

/// Created by changlei on 2024/4/23.
///
/// See [Size]
class PxSize extends PxOffsetBase implements _Px<Size> {
  /// See [Size.new]
  const PxSize(super.width, super.height);

  /// See [Size.copy]
  PxSize.copy(PxSize source) : super(source.width, source.height);

  /// See [Size.square]
  const PxSize.square(PxDouble dimension) : super(dimension, dimension);

  /// See [Size.fromWidth]
  const PxSize.fromWidth(PxDouble width) : super(width, PxDouble.infinity);

  /// See [Size.fromHeight]
  const PxSize.fromHeight(PxDouble height) : super(PxDouble.infinity, height);

  /// See [Size.fromRadius]
  const factory PxSize.fromRadius(PxDouble radius) = _PxSizeFromRadius;

  /// See [Size.width]
  PxDouble get width => _dx;

  /// See [Size.height]
  PxDouble get height => _dy;

  /// See [Size.aspectRatio]
  double get aspectRatio {
    if (height != Px.zero) {
      return width._ / height._;
    }
    if (width > Px.zero) {
      return double.infinity;
    }
    if (width < Px.zero) {
      return double.negativeInfinity;
    }
    return 0;
  }

  /// See [Size.zero]
  static const PxSize zero = PxSize(PxDouble.zero, PxDouble.zero);

  /// See [Size.infinite]
  static const PxSize infinite = PxSize(PxDouble.infinity, PxDouble.infinity);

  /// See [Size.isEmpty]
  bool get isEmpty => width <= 0.0.px || height <= 0.0.px;

  /// See [Size.-]
  PxOffsetBase operator -(PxOffsetBase other) {
    if (other is PxSize) {
      return PxOffset(width - other.width, height - other.height);
    } else {
      return PxSize(width - (other as PxOffset).dx, height - other.dy);
    }
  }

  /// See [Size.+]
  PxSize operator +(PxOffset other) => PxSize(width + other.dx, height + other.dy);

  /// See [Size.*]
  PxSize operator *(PxDouble operand) => PxSize(width * operand, height * operand);

  /// See [Size./]
  PxSize operator /(PxDouble operand) => PxSize(width / operand, height / operand);

  /// See [Size.~/]
  PxSize operator ~/(PxDouble operand) => PxSize((width ~/ operand).toDouble(), (height ~/ operand).toDouble());

  /// See [Size.%]
  PxSize operator %(PxDouble operand) => PxSize(width % operand, height % operand);

  /// See [Size.shortestSide]
  PxDouble get shortestSide => min(width.abs(), height.abs());

  /// See [Size.longestSide]
  PxDouble get longestSide => max(width.abs(), height.abs());

  /// See [Size.topLeft]
  PxOffset topLeft(PxOffset origin) => origin;

  /// See [Size.topCenter]
  PxOffset topCenter(PxOffset origin) => PxOffset(origin.dx + width / 2.0.px, origin.dy);

  /// See [Size.topRight]
  PxOffset topRight(PxOffset origin) => PxOffset(origin.dx + width, origin.dy);

  /// See [Size.centerLeft]
  PxOffset centerLeft(PxOffset origin) => PxOffset(origin.dx, origin.dy + height / 2.0.px);

  /// See [Size.center]
  PxOffset center(PxOffset origin) => PxOffset(origin.dx + width / 2.0.px, origin.dy + height / 2.0.px);

  /// See [Size.centerRight]
  PxOffset centerRight(PxOffset origin) => PxOffset(origin.dx + width, origin.dy + height / 2.0.px);

  /// See [Size.bottomLeft]
  PxOffset bottomLeft(PxOffset origin) => PxOffset(origin.dx, origin.dy + height);

  /// See [Size.bottomCenter]
  PxOffset bottomCenter(PxOffset origin) => PxOffset(origin.dx + width / 2.0.px, origin.dy + height);

  /// See [Size.bottomRight]
  PxOffset bottomRight(PxOffset origin) => PxOffset(origin.dx + width, origin.dy + height);

  /// See [Size.contains]
  bool contains(PxOffset offset) {
    return offset.dx >= 0.0.px && offset.dx < width && offset.dy >= 0.0.px && offset.dy < height;
  }

  /// See [Size.flipped]
  PxSize get flipped => PxSize(height, width);

  /// See [Size.lerp]
  static PxSize? lerp(PxSize? a, PxSize? b, double t) {
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        return a * (1.0 - t).px;
      }
    } else {
      if (a == null) {
        return b * t.px;
      } else {
        return PxSize(_lerpPxDouble(a.width, b.width, t), _lerpPxDouble(a.height, b.height, t));
      }
    }
  }

  @override
  Size toDp(BuildContext context) {
    return Size(
      width.toDp(context),
      height.toDp(context),
    );
  }

  @override
  bool operator ==(Object other) {
    return other is PxSize && other._dx == _dx && other._dy == _dy;
  }

  @override
  int get hashCode => Object.hash(_dx, _dy);

  @override
  String toString() => 'PxSize(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})';
}
